#ifndef MATRIX_H_
#define MATRIX_H_

#include "complex.h"

typedef enum
{
	MATRIX_SUCCESS,
	MATRIX_OUT_OF_MEMORY,
	MATRIX_NULL_ARGUMENT,
	MATRIX_BAD_DIMENSIONS,
	MATRIX_OUT_OF_BOUNDS,
	MATRIX_SINGULAR
} MatrixResult;

typedef struct matrix_t
{
	int rows;
	int columns;
	Complex *matrixHead;
} *Matrix;

/**
 * Creates a matrix. Initializes all its elements to 0.
 * @param rows - Number of rows in created matrix.
 * @param columns - Number of columns in created matrix.
 * @return Initialized matrix if successful, \p NULL otherwise. \n
 * Should return \p NULL if \p rows and/or \p columns are not positive.
 */
Matrix matrixCreate( int rows, int columns );

/**
 * Creates a duplicate of a matrix.
 * @param matrix - The matrix to duplicate.
 * @return An independent copy of \p matrix (in particular, if \p matrix is
 * destroyed, it will not affect its duplicate). \n If an error occurs, returns
 * \p NULL.
 */
Matrix matrixCopy( Matrix matrix );

/**
 * Creates a unit matrix of specified size. A unit matrix is a square matrix
 * whose all off-diagonal elements are 0, and all diagonal elements are 1.
 * @param size - The size of the unit matrix to create.
 * @return An initialized unit matrix of size <tt>size*size</tt> if successful,
 * \p NULL otherwise.
 */
Matrix matrixCreateUnit( int size );

/**
 * Frees all memory associated with \p matrix.
 * \note Should NOT fail in case \p matrix is \p NULL.
 * @param matrix - The matrix whose memory to free.
 */
void matrixDestroy( Matrix matrix );

/**
 * Get matrix's element (r,c) - that is, the one on the r'th row, c'th column.\n
 * Numbers are zero-based, that is, 0 is the first row.
 * @param matrix - The matrix to access.
 * @param row - The row of the element.
 * @param column - The column of the element.
 * @return The element (r,c) [r and c are zero-based] is successful, otherwise
 * an invalid complex (such as the one returned by \p complexCreateInvalid).
 */
Complex matrixGet( Matrix matrix, int row, int column );

/**
 * Set matrix's element (r,c) - that is, the one on the r'th row, c'th column.\n
 * Numbers are zero-based, that is, 0 is the first row.
 * @param matrix - The matrix to access.
 * @param row - The row of the element.
 * @param column - The column of the element.
 * @param value - The value that element (r,c) should be set to.
 * @return One of the following, in decreasing priority:
 * 		 \li \c MATRIX_SUCCESS - Success.
 * 		 \li \c MATRIX_NULL_ARGUMENT - \p matrix is \p NULL.
 * 		 \li \c MATRIX_OUT_OF_BOUNDS - the coordinate (r,c) is not a valid one
 * 			[0 <= r <= rows-1, 0 <= c <= columns-1]
 *
 */
MatrixResult matrixSet( Matrix matrix, int row, int column, Complex value );

/**
 * Performs matrix1 += matrix2, that is: adds matrix1 and matrix2 and stores
 * the result in matrix1. \n "Addition" = regular matrix addition, that is,\par
 * 		matrix1(r,c) <== matrix1(r,c) + matrix2(r,c)
 * @param matrix1 - First matrix; result is stored in it.
 * @param matrix2 - Second matrix; should NOT be modified.
 * @return One of the following, in decreasing priority:
 * 		\li \c MATRIX_SUCCESS - Success.
 * 		\li \c MATRIX_NULL_ARGUMENT - Either matrix (or both) is \p NULL.
 * 		\li \c MATRIX_BAD_DIMENSIONS - Matrices do not have the same dimensions.
 */
MatrixResult matrixAdd( Matrix matrix1, Matrix matrix2 );

/**
 * Performs matrix = -matrix, that is:\par
 * 		matrix(r,c) <== -matrix(r,c)
 * @param matrix
 * @return One of the following, in decreasing priority:
 * 		\li \c MATRIX_SUCCESS - Success.
 * 		\li \c MATRIX_NULL_ARGUMENT - \p matrix is \p NULL.
 */
MatrixResult matrixNegate( Matrix matrix );

/**
 * Performs matrix1 -= matrix2, that is: subtracts matrix2 from matrix1 and
 * stores the result in matrix1. \n "Subtraction" = regular matrix subtraction:
 * 		\par
 * 		matrix1(r,c) <== matrix1(r,c) - matrix2(r,c)
 * @param matrix1 - First matrix; result is stored in it.
 * @param matrix2 - Second matrix; should NOT be modified.
 * @return One of the following, in decreasing priority:
 * 		\li \c MATRIX_SUCCESS - Success.
 * 		\li \c MATRIX_NULL_ARGUMENT - Either matrix (or both) is \p NULL.
 * 		\li \c MATRIX_BAD_DIMENSIONS - Matrices do not have the same dimensions.
 */
MatrixResult matrixSubtract( Matrix matrix1, Matrix matrix2 );

/**
 * Checks whether the two matrices are equal, both in dimensions and contents.
 * Should return true for any two matrices sharing the same dimensions and
 * elements: \par
 * 		matrix1(r,c) equals matrix2(r,c) for all valid r and c.
 * \note Should return true for matrixIsEqual( matrix, matrix );
 * \note Should return true for matrixIsEqual( \p NULL, \p NULL );
 * @param matrix1 - First matrix. Should NOT be modified.
 * @param matrix2 - Second matrix. Should NOT be modified.
 * @return True if the matrices are equal, or both \p NULL. False in all other
 * cases, including but not limited to: \li (1) either matrix is \p NULL (but not
 * both), (2) matrices do not have the same dimensions, and (3) there exists r
 * and c such that
 * 		matrix1(r,c) does not equal matrix2(r,c)
 */
bool matrixIsEqual( Matrix matrix1, Matrix matrix2 );

/**
 * Performs matrix multiplication. Computes matrix1 * matrix2 and stores the
 * result in a newly-created matrix.
 * \par Usage example:
 * \code
 * int main()
 * {
 *      Matrix matrix1 = ...;
 *      Matrix matrix2 = ...;
 *      Matrix result = NULL;
 *
 *      matrixMultiply( matrix1, matrix2, &result );
 *      // result now points to a newly
 *      // created matrix that contains
 *      // the product of the matrices.
 *
 *      ...
 *      matrixDestroy( result );
 *      ...
 * }
 * \endcode
 * \note Generally, matrix1 * matrix2 does not equal to matrix2 * matrix1.
 * \note A new matrix is created, and the caller is responsible for freeing it.
 * @param matrix1 - First matrix to multiply; should NOT be modified.
 * @param matrix2 - Second matrix to multiply; should NOT be modified.
 * @param result - A newly-created matrix that contains the result of the
 * multiplication will be created, and result will point to it. (You can look at
 * it as if this is how this function returns the resulting matrix)
 * @return One of the following, in decreasing priority:
 * 		\li \c MATRIX_SUCCESS - Success.
 * 		\li \c MATRIX_NULL_ARGUMENT - Either matrix (or both), or result, is
 * 				\p NULL.
 * 		\li \c MATRIX_BAD_DIMENSIONS - Matrices do not have compatible
 * 			dimensions; that is, number of columns of \p matrix1 is different
 * 			than the number of rows of \p matrix2.
 */
MatrixResult matrixMultiply( Matrix matrix1, Matrix matrix2, Matrix* result );

/**
 * Swaps rows \p row1 and \p row2 in \p matrix, i.e. Ri <-> Rj from Algebra A:
 * 		\par
 * 		matrix(row1,c) <-> matrix(row2,c) for all 0 <= c <= columns-1
 * \note Modifies \p matrix.
 * \note Should work on non-square matrices.
 * @param matrix - The matrix to operate on.
 * @param row1 - First row.
 * @param row2 - Second row.
 * @return One of the following, in decreasing priority:
 * 		\li \c MATRIX_SUCCESS - Success.
 * 		\li \c MATRIX_NULL_ARGUMENT - \p matrix is \p NULL.
 * 		\li \c MATRIX_OUT_OF_BOUNDS - either \p row1 or \p row2 (or both) are
 * 			invalid zero-based row numbers.
 */
MatrixResult matrixSwapRows( Matrix matrix, int row1, int row2 );

/**
 * Multiplies a row by a scalar, i.e. Ri <- Ri*scalar from Algebra A : \par
 * 		matrix(row,c) <== matrix(row,c) * scalar for all 0 <= c <= columns-1
 * \note Modifies matrix.
 * \note Should work on non-square matrices.
 * @param matrix - The matrix to operate on.
 * @param row - The row to multiply.
 * @param scalar - The scalar to multiply by.
 * @return One of the following, in decreasing priority:
 * 		\li \c MATRIX_SUCCESS - Success.
 * 		\li \c MATRIX_NULL_ARGUMENT - \p matrix is \p NULL.
 * 		\li \c MATRIX_OUT_OF_BOUNDS - \p row is an invalid zero-based row number.
 */
MatrixResult matrixMultiplyRowByScalar( Matrix matrix, int row,
		Complex scalar );

/**
 * Subtracts \p row2 multiplied by scalar from \p row1,
 * i.e. Ri <- Ri - Rj * scalar: \par
 *		matrix(row1,c) <== matrix(row1,c) - scalar * matrix(row2,c) for all c
 * \note Modifies matrix.
 * \note Should work on non-square matrices.
 * @param matrix - The matrix to operate on.
 * @param row1 - First row.
 * @param row2 - Second row.
 * @param scalar - The scalar to multiply by.
 * @return One of the following, in decreasing priority:
 * 		\li \c MATRIX_SUCCESS - Success.
 * 		\li \c MATRIX_NULL_ARGUMENT - \p matrix is \p NULL.
 * 		\li \c MATRIX_OUT_OF_BOUNDS - either \p row1 or \p row2 (or both) are
 * 			invalid zero-based row numbers.
 */
MatrixResult matrixSubtractRow( Matrix matrix, int row1, int row2,
		Complex scalar );

/**
 * Performs matrix inversions. Computes Inverse(matrix) = matrix ^ -1 and
 * stores the result in a newly-created matrix.
 * \par Usage example:
 * \code
 * int main()
 * {
 *      Matrix matrix1 = ...;
 *      Matrix result = NULL;
 *
 *      matrixInverse( matrix1, &result );
 *      // result now points to a newly
 *      // created matrix that contains
 *      // the inverse of matrix1.
 *
 *      ...
 *      matrixDestroy( result );
 *      ...
 * }
 * \endcode
  * \note Works for square matrices only.
 * \note A new matrix is created, and the caller is responsible for freeing it.
 * @param matrix - The matrix to inverse.
 * @param result - A newly-created matrix that contains the result of the
 * inversion will be created, and result will point to it. (You can look at
 * it as if this is how this function returns the resulting matrix)
 * @return One of the following, in decreasing priority:
 * 		\li \c MATRIX_SUCCESS - Great Success.
 * 		\li \c MATRIX_NULL_ARGUMENT - \p matrix or \p result (or both) is
 * 			\p NULL.
 * 		\li \c MATRIX_BAD_DIMENSIONS - \p matrix is not square.
 * 		\li \c MATRIX_SINGULAR - \p matrix is singular (has no inverse).
 */
MatrixResult matrixInverse( Matrix matrix, Matrix* result );

/**
 * Solve a system of linear equations, Ax=b; that is, given a matrix A and a
 * vector b (that is, a matrix with one column) finds a vector x such that
 * Ax = b. \n
 * \par Reminder:
 * A system of linear equations Ax=b has a single solution if and
 * only if \p A is non-singular (has an inverse), and in that case, the solution
 * is x = A^-1 * b (just multiply by A^-1 [from the left] both terms).
 * \par Usage example:
 * \code
 * int main()
 * {
 *      Matrix A = ...;
 *      Matrix b = ...;
 *      Matrix x = NULL;
 *
 *      matrixSolveEquationSystem( A,  b, &x );
 *      // result now points to a newly
 *      // created matrix that contains
 *      // the solution of the system.
 *
 *      ...
 *      matrixDestroy( x );
 *      ...
 * }
 * \endcode
 * \note Works for square A's only.
 * \note A new matrix is created, and the caller is responsible for freeing it.
 * @param A - a square matrix.
 * @param b - a column vector of size Nx1, where N is the size of A.
 * @param x - A newly-created matrix that contains the result of the
 * system will be created, and x will point to it. (You can look at
 * it as if this is how this function returns the resulting matrix)
 * @return One of the following, in decreasing priority:
 * 		\li \c MATRIX_SUCCESS - Great Success.
 * 		\li \c MATRIX_NULL_ARGUMENT - \p A, \p b and/or \p x are \p NULL.
 * 		\li \c MATRIX_BAD_DIMENSIONS - \p A is not square.
 * 		\li \c MATRIX_SINGULAR - \p A is singular (has no inverse).
 */
MatrixResult matrixSolveEquationSystem( Matrix A, Matrix b, Matrix* x );

#endif /* MATRIX_H_ */
